import math

import matplotlib.pyplot as plt

from Path import Path
from params import *
from graph import gen_points, radius
from UnloadingPoint import UnloadingPoint
from utils import time_now, increase_worst_cost, increase_improved_cost


class DistributionCenter:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.id = f'DC({x:.2f}, {y:.2f})'
        self.unloading_points_num = n_unloading_points

        self.unloading_points = [
            UnloadingPoint(up_x, up_y) for up_x, up_y in gen_points(x, y, self.unloading_points_num)
        ]

        self.finished_paths = {}

        self.distance_matrix = {}

    def calculate_distance_matrix(self):
        """计算卸货点与点之间的邻接矩阵"""
        for point in self.unloading_points:
            tmp = {}
            for other_point in self.unloading_points:
                tmp[other_point.id] = math.sqrt((point.x - other_point.x) ** 2 + (point.y - other_point.y) ** 2)

            tmp[self.id] = math.sqrt((point.x - self.x) ** 2 + (point.y - self.y) ** 2)

            self.distance_matrix[point.id] = dict(sorted(tmp.items(), key=lambda item: item[1]))

        tmp = {self.id: 0}
        for other_point in self.unloading_points:
            tmp[other_point.id] = math.sqrt((self.x - other_point.x) ** 2 + (self.y - other_point.y) ** 2)
        self.distance_matrix[self.id] = dict(sorted(tmp.items(), key=lambda item: item[1]))

    def update_orders(self):
        """更新订单"""
        for point in self.unloading_points:
            point.gen_orders()

    def decision_making(self):
        """决策"""
        self.finished_paths[time_now()] = []
        # 将每个配送点上的订单视为单独的路径，即一个配送点代表一条路径
        paths_list = []
        # 本次路径分配是否存在需要配送的订单
        flag = False
        for point in self.unloading_points:
            # 如果当前卸货点累积订单超过了无人机单次能配送的容量n，就把过期时间最早的n个订单一起送了
            while point.orders_num >= max_orders_per_uav:
                self.finished_paths[time_now()].append(Path(self.id, point.pop_nearest_n_orders(), self.distance_matrix))

                # 计算总路径
                path_tmp = self.finished_paths[time_now()][-1]
                increase_improved_cost(path_tmp.min_mileage)
                for order in path_tmp.orders_list:
                    increase_worst_cost(self.distance_matrix[self.id][order.belongs_to]*2)

            if point.orders_num == 0:
                continue
            # 目前卸货点中的累积订单不超过无人机单次配送容量
            new_path = Path(self.id, point.orders_list, self.distance_matrix)
            paths_list.append(new_path)
            if new_path.need_be_distributed:
                flag = True

        if not flag:
            return

        # 现在得到了k个路径，尝试合并相邻的路径，使得当前被配送的订单的平均配送路径最小
        flag = True
        while flag:
            flag = False

            # 先计算当前所有订单的总配送路径
            distributed_orders_count = 0
            distributed_mileage = 0
            for path in paths_list:
                distributed_orders_count += path.orders_num
                distributed_mileage += path.min_mileage

            # 再计算某两个路径组合后有没有优化
            best_ij = None
            new_path = None
            best_mileage = distributed_mileage
            for i in range(len(paths_list)-1):
                for j in range(i+1, len(paths_list)):
                    # 合并两条路径
                    tmp_path = paths_list[i].merge(paths_list[j], self.distance_matrix)
                    # 如果合并成功
                    if tmp_path is not None:
                        # 是否有优化
                        new_distributed_mileage = distributed_mileage - paths_list[i].min_mileage
                        new_distributed_mileage -= paths_list[j].min_mileage
                        new_distributed_mileage += tmp_path.min_mileage

                        # 与上一次最好的平均配送路径比较
                        if new_distributed_mileage < best_mileage:
                            best_ij = i, j
                            new_path = tmp_path
                            best_mileage = new_distributed_mileage
                            flag = True

            # 如果此次合并出了更优的路径
            if best_ij is not None:
                # 删除原来被合并的路径
                paths_list.pop(best_ij[1])
                paths_list.pop(best_ij[0])
                # 添加新的路径
                paths_list.insert(best_ij[0], new_path)

        # 计算当前组合的每个订单平均配送路径
        distributed_orders_count = 0
        distributed_mileage = 0
        for path in paths_list:
            distributed_orders_count += path.orders_num
            distributed_mileage += path.min_mileage

        # 根据合并结果，选择需要配送的路径
        for path in paths_list:
            # 需要被配送的路径
            if path.need_be_distributed:
                # 从卸货点中删除被配送掉的订单
                for order in path.orders_list:
                    order.up.orders_list.remove(order)
                self.finished_paths[time_now()].append(path)

                # 计算总路径
                increase_improved_cost(path.min_mileage)
                for order in path.orders_list:
                    increase_worst_cost(self.distance_matrix[self.id][order.belongs_to]*2)
            # else:
            #     # 当前时间不需要被配送的路径，但是单个订单的平均配送距离小于了全局的平均配送距离
            #     if path.min_mileage / path.orders_num <= distributed_mileage / distributed_orders_count:
            #         for order in path.orders_list:
            #             order.up.orders_list.remove(order)
            #         self.finished_paths[time_now()].append(path)
            #         # print(True)

    def draw_fig(self):
        # Set the size of the plot
        plt.figure()
        plt.title(f'Distribution center ({self.x}, {self.y})')
        # Extract x and y coordinates of points
        points = [point.site for point in self.unloading_points]
        x_points, y_points = zip(*points)

        # Plot the circle with blue outline
        circle = plt.Circle((self.x, self.y), radius, edgecolor='blue', facecolor='none')
        plt.gca().add_patch(circle)

        # Plot the points
        plt.scatter(x_points, y_points, color='red')

        # Plot the center point
        plt.scatter(self.x, self.y, color='blue')

        # Set the limits and aspect ratio of the plot
        plt.xlim(0, 20)
        plt.ylim(0, 20)
        plt.gca().set_aspect('equal', adjustable='box')

        # Add grid to the background
        plt.grid(True)

        # Show the plot
        plt.show()

    def find_point_by_id(self, pid: str):
        if self.id == pid:
            return self.x, self.y
        for point in self.unloading_points:
            if point.id == pid:
                return point.site

    def draw_fig_with_path(self, paths_list):
        # Set the size of the plot
        plt.figure()
        plt.title(f'Distribution center ({self.x}, {self.y})')
        # Extract x and y coordinates of points
        points = [point.site for point in self.unloading_points]
        x_points, y_points = zip(*points)

        # Plot the circle with blue outline
        circle = plt.Circle((self.x, self.y), radius, edgecolor='blue', facecolor='none')
        plt.gca().add_patch(circle)

        # Plot the points
        plt.scatter(x_points, y_points, color='red')

        # Plot the center point
        plt.scatter(self.x, self.y, color='blue')

        # plot the path
        for path in paths_list:
            for i in range(len(path.traverse_order)-1):
                j = i + 1
                x = [self.find_point_by_id(path.traverse_order[i])[0], self.find_point_by_id(path.traverse_order[j])[0]]
                y = [self.find_point_by_id(path.traverse_order[i])[1], self.find_point_by_id(path.traverse_order[j])[1]]
                plt.plot(x, y)

        # Set the limits and aspect ratio of the plot
        plt.xlim(0, 20)
        plt.ylim(0, 20)
        plt.gca().set_aspect('equal', adjustable='box')

        # Add grid to the background
        plt.grid(True)

        # Show the plot
        plt.show()